﻿using System.Collections.Generic;
using System.Linq;
using Styx;
using Styx.CommonBot;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;

namespace FarmerJoe.Helpers
{
    internal class Healing : Targeting
    {
        //private static readonly WaitTimer _tankReset = WaitTimer.ThirtySeconds;
        //private static ulong _tankGuid;

        static Healing()
        {
            // Make sure we have a singleton instance!
            Instance = new Healing();
        }

        public new static Healing Instance { get; private set; }

        public static bool NeedHealTargeting { get; set; }

        public List<WoWPlayer> HealList { get { return ObjectList.ConvertAll(o => o.ToPlayer()); } }

        protected override List<WoWObject> GetInitialObjectList()
        {
            // Targeting requires a list of WoWObjects - so it's not bound to any specific type of object. Just casting it down to WoWObject will work fine.
            return ObjectManager.ObjectList.Where(o => o is WoWPlayer).ToList();
        }

        protected override void DefaultIncludeTargetsFilter(List<WoWObject> incomingUnits, HashSet<WoWObject> outgoingUnits)
        {
            foreach (WoWObject incomingUnit in incomingUnits)
            {
                outgoingUnits.Add(incomingUnit);
            }
        }

        protected override void DefaultRemoveTargetsFilter(List<WoWObject> units)
        {
            bool isHorde = StyxWoW.Me.IsHorde;
            for (int i = units.Count - 1; i >= 0; i--)
            {
                WoWObject o = units[i];
                if (!(o is WoWPlayer))
                {
                    units.RemoveAt(i);
                    continue;
                }

                WoWPlayer player = o.ToPlayer();

                // Make sure we ignore dead/ghost players. If we need res logic, they need to be in the class-specific area.
                if (player.IsDead || player.IsGhost)
                {
                    units.RemoveAt(i);
                    continue;
                }

                // Check if they're hostile first. This should remove enemy players, but it's more of a sanity check than anything.
                if (player.IsHostile)
                {
                    units.RemoveAt(i);
                    continue;
                }

                // If we're horde, and they're not, fuggin ignore them!
                if (player.IsHorde != isHorde)
                {
                    units.RemoveAt(i);
                    continue;
                }

                // They're not in our party/raid. So ignore them. We can't heal them anyway.
                if (!player.IsInMyPartyOrRaid)
                {
                    units.RemoveAt(i);
                    continue;
                }

                //
                // if (p.HealthPercent >= FpswareSettings.Instance.IgnoreHealTargetsAboveHealth)
                //
                if (player.HealthPercent >= 98)
                {
                    units.RemoveAt(i);
                    continue;
                }

                // If we have movement turned off, ignore people who aren't in range.
                // Almost all healing is 40 yards, so we'll use that. If in Battlegrounds use a slightly larger value to expane our 
                // healing range, but not too large that we are running all over the bg zone 
                // note: reordered following tests so only one floating point distance comparison done due to evalution of DisableAllMovement
                if ((ClassParser.CCControlledMovement && player.DistanceSqr > 40 * 40) || player.DistanceSqr > 55 * 55)
                {
                    units.RemoveAt(i);
                    continue;
                }
            }

            // A little bit of a hack, but this ensures 'Me' is in the list.
            //
            //
            // if (!units.Any(o => o.IsMe) && StyxWoW.Me.HealthPercent < FpswareSettings.Instance.IgnoreHealTargetsAboveHealth)
            if (!units.Any(o => o.IsMe) && StyxWoW.Me.HealthPercent < 95)
            {
                units.Add(StyxWoW.Me);
            }
        }

        protected override void DefaultTargetWeight(List<TargetPriority> units)
        {
            var tanks = GetMainTankGuids();
            var inBg = Battlegrounds.IsInsideBattleground;
            foreach (TargetPriority prio in units)
            {
                prio.Score = 500f;
                WoWPlayer player = prio.Object.ToPlayer();

                // The more health they have, the lower the score.
                // This should give -500 for units at 100%
                // And -50 for units at 10%
                prio.Score -= player.HealthPercent * 5;

                // If they're out of range, give them a bit lower score.
                if (player.DistanceSqr > 40 * 40)
                {
                    prio.Score -= 50f;
                }

                // If they're out of LOS, again, lower score!
                if (!player.InLineOfSpellSight)
                {
                    prio.Score -= 100f;
                }

                // Give tanks more weight. If the tank dies, we all die. KEEP HIM UP.
                // Ignore giving more weight to the tank if we have Beacon of Light on it.
                if (tanks.Contains(player.Guid) && player.HealthPercent != 100 && !player.Auras.Any(a => a.Key == "Beacon of Light" && a.Value.CreatorGuid == StyxWoW.Me.Guid))
                {
                    prio.Score += 100f;
                }

                // Give flag carriers more weight in battlegrounds. We need to keep them alive!
                if (inBg && player.Auras.Keys.Any(a => a.ToLowerInvariant().Contains("flag")))
                {
                    prio.Score += 100f;
                }
            }
        }

        private static HashSet<ulong> GetMainTankGuids()
        {
            var infos = StyxWoW.Me.GroupInfo.RaidMembers;

            return new HashSet<ulong>( from pi in infos where (pi.Role & WoWPartyMember.GroupRole.Tank) != 0 select pi.Guid);
        }
    }
}
